package com.rtsimcloud.model.plugin;

import com.rtsimcloud.model.base.Field;
import com.rtsimcloud.morpheus.fom.*;
import com.rtsimcloud.morpheus.fom.datatype.*;
import com.rtsimcloud.morpheus.fom.utils.FomParser;
import freemarker.cache.ClassTemplateLoader;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import freemarker.template.TemplateExceptionHandler;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;

import java.io.*;
import java.text.SimpleDateFormat;
import java.util.*;

/**
 * 解析fom，生成object Class的插件
 *
 * @author jilanyang
 * @date 2021/9/26 14:14
 */
@Mojo(name = "model-generate", defaultPhase = LifecyclePhase.COMPILE)
public class ObjectClassGeneratorMojo extends AbstractMojo {
    /**
     * 项目构建输出的根目录，必填
     */
    @Parameter(name = "outputDirectory", required = true)
    private String outputDirectory;

    /**
     * 项目构建资源文件根目录
     */
    @Parameter(name = "srcDirectory", required = true, readonly = true)
    private String srcDirectory;

    /**
     * 项目根目录
     */
    @Parameter(name = "baseDir", required = true, readonly = true)
    private File baseDir;

    /**
     * fom文件名
     */
    @Parameter(name = "fomName", required = true, readonly = true)
    private String fomName;

    /**
     * 包路径
     */
    @Parameter(name = "dataTypePackagePath", required = true, readonly = true)
    private String dataTypePackagePath;

    private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    /**
     * 对象类包名
     * 生成路径：basePackageName.objectClassPackageName.ObjectClass
     */
    private final String objectClassPackageName = "objects";

    /**
     * 交互类包名
     * 生成路径：basePackageName.interactionPackageName.InteractionClass
     */
    private final String interactionPackageName = "interactions";

    /**
     * 数据类型包名
     * 生成路径：basePackageName.dataTypesPackageName.DataType
     */
    private final String dataTypesPackageName = "datatypes";

    @Override
    public void execute() throws MojoExecutionException, MojoFailureException {
        // 输入路径
        String srcPath = getSrcDirectory() + srcDirectory;
        // 输出路径
        String outputPath = getOutputDirectory() + outputDirectory;
        // 包名
        String basePackageName = outputDirectory.replace("/", ".");
        // 去除最后一个多余的点
        if (basePackageName.endsWith(".")) {
            basePackageName = basePackageName.substring(0, basePackageName.length() - 1);
        }

        try {
            // 声明模版引擎freemarker配置类
            Configuration configuration = new Configuration(Configuration.VERSION_2_3_31);
            // 配置模版路径
            configuration.setClassForTemplateLoading(this.getClass(), "/template/");
            configuration.setTemplateLoader(new ClassTemplateLoader(this.getClass(), "/template/"));
            //cfg.setDirectoryForTemplateLoading(new File(getSourcePath()));
            configuration.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);
            // 设置字符编码
            configuration.setDefaultEncoding("UTF-8");

            // fom解析
            ObjectModel objectModel = null;
            File file = new File(srcPath + fomName);
            // 目录文件校验
            checkFile(file);
            if (file.isDirectory()) {
                File[] files = file.listFiles();
                List<ObjectModel> modelList = new ArrayList<>(files.length);
                for (File fomFile : files) {
                    modelList.add(FomParser.parse(fomFile.toURI().toURL()));
                }
                objectModel = ModelMerger.merge(modelList);
            } else {
                objectModel = FomParser.parse(file.toURI().toURL());
            }

            // 生成
            doGenerate(objectModel, configuration, basePackageName, outputPath);
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    /**
     * 生成逻辑
     *
     * @param objectModel
     * @param configuration freemarker配置类
     * @param packageName   包名
     * @param outputPath    输出路径
     */
    private void doGenerate(ObjectModel objectModel, Configuration configuration, String packageName, String outputPath) {
        try {
            // 加载模版文件
            Template objectClassTemplate = configuration.getTemplate("ObjectClassTemplate.ftl");
            Template dataTypeTemplate = configuration.getTemplate("DataTypeTemplate.ftl");
            Template interactionClassTemplate = configuration.getTemplate("InteractionClassTemplate.ftl");

            // 生成数据类型
            generateDataTypes(objectModel, packageName, outputPath, dataTypeTemplate);

            // 生成对象类
            generateObjects(objectModel, packageName, outputPath, objectClassTemplate);

            // 生成交互类
            generateInteractions(objectModel, packageName, outputPath, interactionClassTemplate);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 生成数据类型
     *
     * @param objectModel
     * @param packageName
     * @param outputPath
     * @param dataTypeTemplate
     */
    private void generateDataTypes(ObjectModel objectModel, String packageName, String outputPath, Template dataTypeTemplate) throws IOException, TemplateException {
        Writer writer;

        Set<IDatatype> datatypes = objectModel.getDatatypes();
        if (datatypes == null || datatypes.isEmpty()) {
            System.err.println("fom中没有定义任何的dataTypes");
            return;
        }

        // 存放freemarker模版数据模型的map，放外面以复用
        Map<String, Object> attrMap = new HashMap<>();
        for (IDatatype datatype : datatypes) {
            // 类型
            DatatypeClass datatypeClass = datatype.getDatatypeClass();
            // 当前类的所在路径
            String currentClassParentPath = String.join("/", outputPath, dataTypesPackageName);
            // 当前类的包
            String currentClassPackage = String.join(".", packageName, dataTypesPackageName);
            // 生成的输出文件
            File generateFile = null;

            if (DatatypeClass.BASIC.equals(datatypeClass)) {
                BasicType basicType = (BasicType) datatype;
                String className = basicType.getName().replaceAll("-", "_");

                // 数据模型
                attrMap.clear();
                attrMap.put("classType", "class");
                attrMap.put("packageName", currentClassPackage);
                attrMap.put("attributes", new ArrayList<>());
                attrMap.put("className", className);
                attrMap.put("attributePackages", new HashSet<>());
                attrMap.put("createTime", DATE_FORMAT.format(new Date()));

                generateFile = new File(currentClassParentPath + "/" + className + ".java");
            } else if (DatatypeClass.SIMPLE.equals(datatypeClass)) {
                SimpleType simpleType = (SimpleType) datatype;
                String className = simpleType.getName().replaceAll("-", "_");
                // 类型
                IDatatype type = simpleType.getRepresentation();

                // 数据模型
                attrMap.clear();
                attrMap.put("classType", "class");
                attrMap.put("packageName", currentClassPackage);
                attrMap.put("attributes", new ArrayList<>());
                attrMap.put("className", className);
                attrMap.put("parentClassName", type.getName().replaceAll("-", "_"));
                attrMap.put("attributePackages", new HashSet<>());
                attrMap.put("createTime", DATE_FORMAT.format(new Date()));

                generateFile = new File(currentClassParentPath + "/" + className + ".java");
            } else if (DatatypeClass.ENUMERATED.equals(datatypeClass)) {
                EnumeratedType enumeratedType = (EnumeratedType) datatype;
                String className = enumeratedType.getName();
                List<Enumerator> enumerators = enumeratedType.getEnumerators();

                // 数据模型
                attrMap.clear();
                attrMap.put("classType", "enum");
                attrMap.put("packageName", currentClassPackage);
                attrMap.put("className", className);
                attrMap.put("attributePackages", new HashSet<>());
                attrMap.put("createTime", DATE_FORMAT.format(new Date()));

                // 枚举对象
                List<Field> enumList = new ArrayList<>(enumerators.size());
                for (Enumerator enumerator : enumerators) {
                    // 枚举对
                    Field field = new Field();
                    field.setDesc(enumerator.getName().replaceAll("-", "_"))
                            .setValue(String.valueOf(enumerator.getValue().intValue()));
                    enumList.add(field);
                }
                attrMap.put("enumList", enumList);

                // 枚举属性
                List<Field> fields = new ArrayList<>(2);
                fields.add(new Field().setType("Integer").setName("value"));
                attrMap.put("attributes", fields);

                generateFile = new File(currentClassParentPath + "/" + className + ".java");
            } else if (DatatypeClass.FIXEDRECORD.equals(datatypeClass)) {
                FixedRecordType fixedRecordType = (FixedRecordType) datatype;
                String className = fixedRecordType.getName();
                List<com.rtsimcloud.morpheus.fom.datatype.Field> fields = fixedRecordType.getFields();

                // 数据模型
                attrMap.clear();
                attrMap.put("classType", "class");
                attrMap.put("packageName", currentClassPackage);
                attrMap.put("className", className);
                attrMap.put("attributePackages", new HashSet<>());
                attrMap.put("createTime", DATE_FORMAT.format(new Date()));
                List<Field> attributes = new ArrayList<>(fields.size());
                for (com.rtsimcloud.morpheus.fom.datatype.Field field : fields) {
                    String fieldName = field.getName();
                    IDatatype fieldType = field.getDatatype();

                    Field attribute = new Field();
                    attribute.setName(fieldName).setType(fieldType.getName());
                    attributes.add(attribute);
                }
                attrMap.put("attributes", attributes);

                generateFile = new File(currentClassParentPath + "/" + className + ".java");
            } else if (DatatypeClass.VARIANTRECORD.equals(datatypeClass)) {
                VariantRecordType variantRecordType = (VariantRecordType) datatype;
                String className = variantRecordType.getName().replaceAll("-", "_");
                // 数据模型
                attrMap.clear();
                attrMap.put("classType", "class");
                attrMap.put("packageName", currentClassPackage);
                attrMap.put("className", className);
                attrMap.put("attributePackages", new HashSet<>());
                attrMap.put("createTime", DATE_FORMAT.format(new Date()));
                attrMap.put("attributes", new ArrayList<>());

                generateFile = new File(currentClassParentPath + "/" + className + ".java");
            } else if (DatatypeClass.ARRAY.equals(datatypeClass)) {
                ArrayType arrayType = (ArrayType) datatype;
                String className = arrayType.getName();
                // 数组元素数据类型
                IDatatype itemDataType = arrayType.getDatatype();

                // 组装数据模型
                attrMap.clear();
                attrMap.put("classType", "class");
                attrMap.put("packageName", currentClassPackage);
                attrMap.put("className", className);
                attrMap.put("attributePackages", new HashSet<>());
                attrMap.put("createTime", DATE_FORMAT.format(new Date()));
                attrMap.put("attributeType", "list");
                List<Field> list = new ArrayList<>(1);
                Field field = new Field();
                field.setName(itemDataType.getName());
                field.setType(itemDataType.getName());
                list.add(field);
                attrMap.put("attributes", list);

                generateFile = new File(currentClassParentPath + "/" + className + ".java");
            } else if (DatatypeClass.NA.equals(datatypeClass)) {
                // System.err.println("NA类型暂时不支持");
            } else {
                System.err.println("非fom标准数据类型：" + datatypeClass);
            }

            // 输出文件所在目录
            File parentDirFile = new File(currentClassParentPath);
            if (!parentDirFile.exists()) {
                boolean mkdirs = parentDirFile.mkdirs();
            }
            if (generateFile != null) {
                // 打印流
                writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(generateFile)));
                // 生成
                dataTypeTemplate.process(attrMap, writer);
                writer.close();
            }
        }
        System.out.println("dataTypes 生成完成...");
    }

    /**
     * 生成交互类
     *
     * @param objectModel
     * @param packageName
     * @param outputPath
     * @param interactionClassTemplate
     */
    private void generateInteractions(ObjectModel objectModel, String packageName, String outputPath, Template interactionClassTemplate) throws IOException, TemplateException {
        // 拿到所有的交互类
        Set<ICMetadata> interactionClasses = objectModel.getAllInteractionClasses();
        if (interactionClasses == null || interactionClasses.isEmpty()) {
            System.err.println("fom中没有定义任何一个interactions class");
            return;
        }

        // 当前类的所在路径
        String currentClassParentPath = String.join("/", outputPath, interactionPackageName);
        File file = new File(currentClassParentPath);
        if (!file.exists()) {
            boolean mkdirs = file.mkdirs();
        }

        Map<String, Object> attrMap = new HashMap<>();
        for (ICMetadata interactionClass : interactionClasses) {
            String className = interactionClass.getLocalName();
            // 父object
            ICMetadata parent = interactionClass.getParent();
            // 获取自己声明的属性
            Set<PCMetadata> allParameters = interactionClass.getAllParameters();

            // 组装模版数据模型
            attrMap.clear();
            attrMap.put("createTime", DATE_FORMAT.format(new Date()));
            attrMap.put("packageName", packageName + "." + interactionPackageName);
            if (parent != null) {
                attrMap.put("parentClassName", parent.getLocalName());
            }
            attrMap.put("className", className);
            List<Field> fields = new ArrayList<>(allParameters.size());
            Set<String> fieldPackageSet = new HashSet<>(allParameters.size());
            if (!allParameters.isEmpty()) {
                for (PCMetadata parameter : allParameters) {
                    String fieldName = parameter.getName();
                    IDatatype parameterDatatype = parameter.getDatatype();

                    Field field = new Field();
                    field.setType(parameterDatatype.getName());
                    field.setName(fieldName);
                    fields.add(field);

                    fieldPackageSet.add(String.join(".", packageName, dataTypesPackageName, parameterDatatype.getName()));
                }
                attrMap.put("attributes", fields);
                attrMap.put("attributePackages", fieldPackageSet);
            } else {
                attrMap.put("attributes", new ArrayList<>());
                attrMap.put("attributePackages", new HashSet<>());
            }

            // 生成文件的地址(输出地址)
            File generateFile = new File(currentClassParentPath + "/" + interactionClass.getLocalName() + ".java");
            Writer writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(generateFile)));
            interactionClassTemplate.process(attrMap, writer);

            // 及时关流
            writer.close();
        }
        System.out.println("interactions class 生成完成...");
    }

    /**
     * 生成对象类
     *
     * @param objectModel
     * @param packageName
     * @param outputPath
     * @param objectClassTemplate
     * @throws TemplateException
     * @throws IOException
     */
    private void generateObjects(ObjectModel objectModel, String packageName, String outputPath, Template objectClassTemplate) throws TemplateException, IOException {
        // 拿到所有的object classes
        Set<OCMetadata> objectClasses = objectModel.getAllObjectClasses();
        if (objectClasses == null || objectClasses.isEmpty()) {
            System.err.println("fom中没有定义任何一个object class");
            return;
        }

        // 当前类的所在路径
        String currentClassParentPath = String.join("/", outputPath, objectClassPackageName);
        File file = new File(currentClassParentPath);
        if (!file.exists()) {
            boolean mkdirs = file.mkdirs();
        }

        Map<String, Object> attrMap = new HashMap<>();
        for (OCMetadata objectClass : objectClasses) {
            String className = objectClass.getLocalName();
            // 父object
            OCMetadata parentObject = objectClass.getParent();
            // 获取自己声明的属性
            Set<ACMetadata> attributes = objectClass.getDeclaredAttributes();

            // 组装数据模型
            attrMap.clear();
            attrMap.put("createTime", DATE_FORMAT.format(new Date()));
            attrMap.put("packageName", packageName + "." + objectClassPackageName);
            if (parentObject != null) {
                attrMap.put("parentClassName", parentObject.getLocalName());
            }
            attrMap.put("className", className);
            if (attributes != null && !attributes.isEmpty()) {
                // 导入的属性包，需要去重
                Set<String> attrPackageSet = new HashSet<>();
                // 属性
                List<Field> attrList = new ArrayList<>(attributes.size());
                for (ACMetadata attribute : attributes) {
                    Field attr = new Field();
                    attr.setPackageName(dataTypePackagePath);
                    attr.setName(attribute.getName());
                    attr.setType(attribute.getDatatype().getName());

                    attrList.add(attr);
                    attrPackageSet.add(dataTypePackagePath + "." + attribute.getDatatype().getName());
                }
                attrMap.put("attributes", attrList);
                attrMap.put("attributePackages", attrPackageSet);
            } else {
                attrMap.put("attributes", new ArrayList<>());
                attrMap.put("attributePackages", new HashSet<>());
            }

            // 生成文件的地址(输出地址)
            File generateFile = new File(currentClassParentPath + "/" + objectClass.getLocalName() + ".java");
            Writer writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(generateFile)));
            objectClassTemplate.process(attrMap, writer);

            // 及时关流
            writer.close();
        }
        System.out.println("object class 生成完成...");
    }

    private void checkFile(File file) {
        String msg = "配置的fom文件不存在，请检查配置路径!";
        if (file == null || !file.exists()) {
            throw new RuntimeException(msg);
        }

        if (file.isDirectory()) {
            File[] subFiles = file.listFiles();
            if (subFiles == null || subFiles.length < 1) {
                throw new RuntimeException(msg);
            }

            for (File subFile : subFiles) {
                if (!subFile.getName().endsWith(".xml")) {
                    throw new RuntimeException("fom文件名必须以xml结尾！");
                }
            }
        }
    }

    private String getSrcDirectory() {
        return String.format("%s/src/main/resources/", baseDir.getAbsolutePath());
    }

    private String getOutputDirectory() {
        return String.format("%s/src/main/java/", baseDir.getAbsolutePath());
    }
}
